/*
    SDL - Simple DirectMedia Layer
    Copyright (C) 1997, 1998, 1999  Sam Lantinga

    This library is SDL_free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    Sam Lantinga
    slouken@devolution.com
*/
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <windows.h>

#include <SDL/SDL.h>
#include "SDL_sysvideo.h"
#include "SDL_dibvideo.h"
#include "SDL_pixels_c.h"

#include <ptgui/ptgui.h>
#include <ptgui/pagectl.h>

/* The window we use for everything... */
const char *SDL_Appname = NULL;
HINSTANCE SDL_Instance;
HWND SDL_Window  = NULL;
WNDPROC	 m_pOldWndProc = NULL;	
RECT SDL_bounds;
int  SDL_resizing = 0;
Uint8 SDL_appstate;

/* Functions called by the message processing function */
LONG
(*HandleMessage)(_THIS, HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
void (*WIN_RealizePalette)(_THIS);
void (*WIN_PaletteChanged)(_THIS, HWND window);
void (*WIN_WinPAINT)(_THIS, HDC hdc);

int posted = 0;

/* Initialization/Query functions */
static int DIB_VideoInit(_THIS, SDL_PixelFormat *vformat);
static SDL_Rect **DIB_ListModes(_THIS, SDL_PixelFormat *format, Uint32 flags);
static SDL_Surface *DIB_SetVideoMode(_THIS, SDL_Surface *current, int width, int height, int bpp, Uint32 flags);
static int DIB_SetColors(_THIS, int firstcolor, int ncolors);
static void DIB_VideoQuit(_THIS);

/* Hardware surface functions */
static int DIB_AllocHWSurface(_THIS, SDL_Surface *surface);
static int DIB_LockHWSurface(_THIS, SDL_Surface *surface);
static void DIB_UnlockHWSurface(_THIS, SDL_Surface *surface);
static void DIB_FreeHWSurface(_THIS, SDL_Surface *surface);

/* Windows message handling functions */
static void DIB_RealizePalette(_THIS);
static void DIB_PaletteChanged(_THIS, HWND window);
static void DIB_WinPAINT(_THIS, HDC hdc);

LONG CALLBACK WinMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

/* DIB driver bootstrap functions */

static int DIB_Available(void)
{
	return(1);
}

static void DIB_DeleteDevice(SDL_VideoDevice *device)
{
	SDL_free(device->hidden);
	SDL_free(device);
}

typedef struct {
	const char *szWindowClass;
	const char *szWindowTitle;
	HINSTANCE hInst;								// current instance
	HWND hWnd;
	WNDPROC SDLProc;
} SDL_DibUserData;

static SDL_VideoDevice *DIB_CreateDevice(int devindex, void *user_data)
{
	SDL_VideoDevice *device;
	SDL_DibUserData *dibUserData;

	/* Initialize all variables that we clean on shutdown */
	device = (SDL_VideoDevice *)SDL_malloc(sizeof(SDL_VideoDevice));
	if ( device ) {
		memset(device, 0, (sizeof *device));
		device->hidden = (struct SDL_PrivateVideoData *)
				SDL_malloc((sizeof *device->hidden));
	}
	if ( (device == NULL) || (device->hidden == NULL) ) {
		SDL_OutOfMemory();
		if ( device ) {
			SDL_free(device);
		}
		return(0);
	}
	memset(device->hidden, 0, (sizeof *device->hidden));

	/* Set the function pointers */
	device->VideoInit = DIB_VideoInit;
	device->ListModes = DIB_ListModes;
	device->SetVideoMode = DIB_SetVideoMode;
	device->SetColors = DIB_SetColors;
	device->UpdateRects = NULL;
	device->VideoQuit = DIB_VideoQuit;
	device->AllocHWSurface = DIB_AllocHWSurface;
	device->CheckHWBlit = NULL;
	device->FillHWRect = NULL;
	device->SetHWColorKey = NULL;
	device->SetHWAlpha = NULL;
	device->LockHWSurface = DIB_LockHWSurface;
	device->UnlockHWSurface = DIB_UnlockHWSurface;
	device->FlipHWSurface = NULL;
	device->FreeHWSurface = DIB_FreeHWSurface;

	device->GetAppState = DIB_GetAppState;

	/* Set up the windows message handling functions */
	WIN_RealizePalette = DIB_RealizePalette;
	WIN_PaletteChanged = DIB_PaletteChanged;
	WIN_WinPAINT = DIB_WinPAINT;
	HandleMessage = DIB_HandleMessage;

	device->free = DIB_DeleteDevice;

	device->user_data = user_data;

	dibUserData = (SDL_DibUserData *)user_data;
	SDL_Appname = dibUserData->szWindowClass;
	SDL_Instance = dibUserData->hInst;
	SDL_Window = dibUserData->hWnd;
	dibUserData->SDLProc = WinMessage;
	SDL_appstate = 0;	

	/* We're finally ready */
	return device;
}

VideoBootStrap WINDIB_bootstrap = {
	"windib", DIB_Available, DIB_CreateDevice
};

int DIB_VideoInit(_THIS, SDL_PixelFormat *vformat)
{
	HDC    hdc;

	/* Determine the screen depth */
	hdc = GetDC(SDL_Window);
	vformat->BitsPerPixel = GetDeviceCaps(hdc, PLANES) * 
						GetDeviceCaps(hdc, BITSPIXEL);
	switch (vformat->BitsPerPixel) {
		case 16:
			/* GDI defined as 5-5-5 */
			vformat->Rmask = 0x00007c00;
			vformat->Gmask = 0x000003e0;
			vformat->Bmask = 0x0000001f;
			break;
		case 24:
		case 32:
			/* GDI defined as 8-8-8 */
			vformat->Rmask = 0x00ff0000;
			vformat->Gmask = 0x0000ff00;
			vformat->Bmask = 0x000000ff;
			break;
		default:
			break;
	}
	ReleaseDC(SDL_Window, hdc);

	/* Grab an identity palette if we are in a palettized mode */
	if ( vformat->BitsPerPixel <= 8 ) {
		LOGPALETTE *palette;
		int ncolors;
		int i;

		ncolors = 1;
		for ( i=0; i<vformat->BitsPerPixel; ++i ) {
			ncolors *= 2;
		}
		palette = (LOGPALETTE *)alloca(sizeof(*palette)+
					ncolors*sizeof(PALETTEENTRY));
		palette->palVersion = 0x300;
		palette->palNumEntries = ncolors;
		hdc = GetDC(SDL_Window);
		GetSystemPaletteEntries(hdc, 0, ncolors, palette->palPalEntry);
		ReleaseDC(SDL_Window, hdc);
		screen_pal = CreatePalette(palette);
	}

	/* Fill in some window manager capabilities */
	this->info.wm_available = 1;

	/* We're done! */
	return(0);
}

/* We support any format at any dimension */
SDL_Rect **DIB_ListModes(_THIS, SDL_PixelFormat *format, Uint32 flags)
{
	return((SDL_Rect **)-1);
}

/* Various screen update functions available */
static void DIB_NormalUpdate(_THIS, int numrects, SDL_Rect *rects);

SDL_Surface *DIB_SetVideoMode(_THIS, SDL_Surface *current,
				int width, int height, int bpp, Uint32 flags)
{
	SDL_Surface *video;
	BITMAPINFO *binfo;
	HDC hdc;
	RECT bounds;
	int x, y;

	/* Recalculate the bitmasks if necessary */
	if ( bpp == current->format->BitsPerPixel ) {
		video = current;
	} else {
		Uint32 Rmask, Gmask, Bmask;

		switch (bpp) {
			case 16:
				/* GDI defined as 5-5-5 */
				Rmask = 0x00007c00;
				Gmask = 0x000003e0;
				Bmask = 0x0000001f;
				break;
			case 24:
			case 32:
				/* GDI defined as 8-8-8 */
				Rmask = 0x00ff0000;
				Gmask = 0x0000ff00;
				Bmask = 0x000000ff;
				break;
			default:
				Rmask = 0x00000000;
				Gmask = 0x00000000;
				Bmask = 0x00000000;
				break;
		}
		video = SDL_CreateRGBSurface(SDL_SWSURFACE,
					0, 0, bpp, Rmask, Gmask, Bmask, 0);
		if ( video == NULL ) {
			SDL_OutOfMemory();
			return(NULL);
		}
	}

	/* Fill in part of the video surface */
	video->flags = 0;	/* Clear flags */
	video->w = width;
	video->h = height;
	video->pitch = SDL_CalculatePitch(video);

	/* Fill in the bitmap info header */
	if ( video->format->palette != NULL ) {
		binfo = (BITMAPINFO *)alloca(sizeof(*binfo) + 
			video->format->palette->ncolors*sizeof(RGBQUAD));
	} else {
		binfo = (BITMAPINFO *)alloca(sizeof(*binfo));
	}
	if ( binfo == NULL ) {
		if ( video != current ) {
			SDL_FreeSurface(video);
		}
		SDL_OutOfMemory();
		return(NULL);
	}
	binfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	binfo->bmiHeader.biWidth = video->w;
	binfo->bmiHeader.biHeight = -video->h;
	binfo->bmiHeader.biPlanes = 1;
	binfo->bmiHeader.biBitCount = video->format->BitsPerPixel;
	binfo->bmiHeader.biCompression = BI_RGB;
	binfo->bmiHeader.biSizeImage = video->h * video->pitch;
	binfo->bmiHeader.biXPelsPerMeter = 0;
	binfo->bmiHeader.biYPelsPerMeter = 0;
	binfo->bmiHeader.biClrUsed = 0;
	binfo->bmiHeader.biClrImportant = 0;
	if ( video->format->palette ) {
		memset(binfo->bmiColors, 0,
			video->format->palette->ncolors*sizeof(RGBQUAD));
	}

	/* Create the offscreen bitmap buffer */
	if ( screen_bmp != NULL ) {
		DeleteObject(screen_bmp);
	}
	hdc = GetDC(SDL_Window);
	screen_bmp = CreateDIBSection(hdc, binfo, DIB_RGB_COLORS,
				(void **)(&video->pixels), NULL, 0);
	ReleaseDC(SDL_Window, hdc);
	if ( screen_bmp == NULL ) {
		if ( video != current ) {
			SDL_FreeSurface(video);
		}
		SDL_SetError("Couldn't create DIB section");
		return(NULL);
	}
	this->UpdateRects = DIB_NormalUpdate;


	/* Set video surface flags */
	if ( bpp <= 8 ) {
		/* BitBlt() maps colors for us */
		video->flags |= SDL_HWPALETTE;
	}

	SDL_resizing = 1;
	bounds.left = 0;
	bounds.top = 0;
	bounds.right = video->w;
	bounds.bottom = video->h;
	AdjustWindowRect(&bounds, GetWindowLong(SDL_Window, GWL_STYLE), FALSE);
	width = bounds.right-bounds.left;
	height = bounds.bottom-bounds.top;
	x = (GetSystemMetrics(SM_CXSCREEN)-width)/2;
	y = (GetSystemMetrics(SM_CYSCREEN)-height)/2;
	if ( y < 0 ) { /* Cover up title bar for more client area */
		y -= GetSystemMetrics(SM_CYCAPTION)/2;
	}
	SetWindowPos(SDL_Window, NULL, x, y, width, height,
		(SWP_NOCOPYBITS | SWP_NOZORDER | SWP_SHOWWINDOW));
	SDL_resizing = 0;
	SetForegroundWindow(SDL_Window);

	/* We're live! */
	return(video);
}

/* We don't actually allow hardware surfaces in the DIB driver */
static int DIB_AllocHWSurface(_THIS, SDL_Surface *surface)
{
	return(-1);
}
static void DIB_FreeHWSurface(_THIS, SDL_Surface *surface)
{
	return;
}
static int DIB_LockHWSurface(_THIS, SDL_Surface *surface)
{
	return(0);
}
static void DIB_UnlockHWSurface(_THIS, SDL_Surface *surface)
{
	return;
}

static void DIB_NormalUpdate(_THIS, int numrects, SDL_Rect *rects)
{
	HDC hdc, mdc;
	int i;

	hdc = GetDC(SDL_Window);
	if ( screen_pal ) {
		SelectPalette(hdc, screen_pal, FALSE);
	}
	mdc = CreateCompatibleDC(hdc);
	SelectObject(mdc, screen_bmp);
	for ( i=0; i<numrects; ++i ) {
		BitBlt(hdc, rects[i].x, rects[i].y, rects[i].w, rects[i].h,
					mdc, rects[i].x, rects[i].y, SRCCOPY);
	}
	DeleteDC(mdc);
	ReleaseDC(SDL_Window, hdc);
}

int DIB_SetColors(_THIS, int firstcolor, int ncolors)
{
	SDL_Palette *palette;
	RGBQUAD *pal;
	int i, j;
	HDC hdc, mdc;

	/* Update the display palette */
	hdc = GetDC(SDL_Window);
	palette = this->screen->format->palette;
	if ( screen_pal ) {
		PALETTEENTRY *entries;

		entries = (PALETTEENTRY *)alloca(ncolors*sizeof(PALETTEENTRY));
		for ( i=0, j=firstcolor; i<ncolors; ++i, ++j ) {
			entries[i].peRed   = palette->colors[j].r;
			entries[i].peGreen = palette->colors[j].g;
			entries[i].peBlue  = palette->colors[j].b;
			entries[i].peFlags = PC_NOCOLLAPSE;
		}
		SetPaletteEntries(screen_pal, firstcolor, ncolors, entries);
		SelectPalette(hdc, screen_pal, FALSE);
		RealizePalette(hdc);
	}

	/* Copy palette colors into DIB palette */
	pal = (RGBQUAD *)alloca(palette->ncolors*sizeof(RGBQUAD));
	for ( i=0; i<palette->ncolors; ++i ) {
		pal[i].rgbRed = palette->colors[i].r;
		pal[i].rgbGreen = palette->colors[i].g;
		pal[i].rgbBlue = palette->colors[i].b;
		pal[i].rgbReserved = 0;
	}

	/* Set the DIB palette and update the display */
	mdc = CreateCompatibleDC(hdc);
	SelectObject(mdc, screen_bmp);
	SetDIBColorTable(mdc, 0, palette->ncolors, pal);
	BitBlt(hdc, 0, 0, this->screen->w, this->screen->h, mdc, 0, 0, SRCCOPY);
	DeleteDC(mdc);
	ReleaseDC(SDL_Window, hdc);
	return(1);
}

void DIB_VideoQuit(_THIS)
{
	/* Destroy the window and everything associated with it */
	if ( SDL_Window ) {
		if (m_pOldWndProc != NULL)	{
			SetWindowLong( SDL_Window,GWL_WNDPROC,( DWORD ) m_pOldWndProc);
		}
		
		DIB_DestroyWindow(this);

		/* Delete the screen bitmap (also frees screen->pixels) */
		if ( screen_bmp ) {
			DeleteObject(screen_bmp);
			screen_bmp = NULL;
		}
		if ( this->screen ) {
			this->screen->pixels = NULL;
		}
		SDL_Window = NULL;
	}
}

/* Exported for the windows message loop only */
static void DIB_FocusPalette(_THIS, int foreground)
{
	if ( screen_pal != NULL ) {
		HDC hdc;

		hdc = GetDC(SDL_Window);
		SelectPalette(hdc, screen_pal, FALSE);
		if ( RealizePalette(hdc) )
			InvalidateRect(SDL_Window, NULL, FALSE);
		ReleaseDC(SDL_Window, hdc);
	}
}
static void DIB_RealizePalette(_THIS)
{
	DIB_FocusPalette(this, 1);
}
static void DIB_PaletteChanged(_THIS, HWND window)
{
	if ( window != SDL_Window ) {
		DIB_FocusPalette(this, 0);
	}
}

/* Exported for the windows message loop only */
static void DIB_WinPAINT(_THIS, HDC hdc)
{
	HDC mdc;

	if ( screen_pal ) {
		SelectPalette(hdc, screen_pal, FALSE);
	}
	mdc = CreateCompatibleDC(hdc);
	SelectObject(mdc, screen_bmp);
	BitBlt(hdc, 0, 0, SDL_VideoSurface->w, SDL_VideoSurface->h,
							mdc, 0, 0, SRCCOPY);
	DeleteDC(mdc);
}

/* The main Win32 event handler */
LONG CALLBACK WinMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	static int mouse_pressed = 0;
	Uint8 is_active;

	switch (msg) {
		case WM_ACTIVATE: {
			is_active=LOWORD(wParam)!=WA_INACTIVE && HIWORD(wParam)==0;
			if (is_active) {
				SDL_appstate = (SDL_APPACTIVE|SDL_APPINPUTFOCUS|SDL_APPMOUSEFOCUS);		
			}
			else {
				SDL_appstate = 0;
			}
		}
		return(0);
		case WM_LBUTTONUP: {
			ptgui_page_controller_do_mouse();
		}
		return(0);
		/* This message is sent as a way for us to "check" the values
		 * of a position change.  If we don't like it, we can adjust
		 * the values before they are changed.
		 */
		case WM_GETMINMAXINFO: {
			MINMAXINFO *info;
			RECT        size;
			int x, y;
			int width;
			int height;

			/* We don't want to clobber an internal resize */
			if ( SDL_resizing )
				return(0);

			/* Get the current position of our window */
			GetWindowRect(SDL_Window, &size);
			x = size.left;
			y = size.top;

			/* Calculate current width and height of our window */
			size.top = 0;
			size.left = 0;
			if ( SDL_PublicSurface != NULL ) {
				size.bottom = SDL_PublicSurface->h;
				size.right = SDL_PublicSurface->w;
			} else {
				size.bottom = 0;
				size.right = 0;
			}
			AdjustWindowRect(&size, GetWindowLong(hwnd, GWL_STYLE),
									FALSE);
			width = size.right - size.left;
			height = size.bottom - size.top;

			/* Fix our size to the current size */
			info = (MINMAXINFO *)lParam;
			info->ptMaxSize.x = width;
			info->ptMaxSize.y = height;
			info->ptMaxPosition.x = x;
			info->ptMaxPosition.y = y;
			info->ptMinTrackSize.x = width;
			info->ptMinTrackSize.y = height;
			info->ptMaxTrackSize.x = width;
			info->ptMaxTrackSize.y = height;
		}
		return(0);

		case WM_MOVE: {
			GetClientRect(SDL_Window, &SDL_bounds);
			ClientToScreen(SDL_Window, (LPPOINT)&SDL_bounds);
			ClientToScreen(SDL_Window, (LPPOINT)&SDL_bounds+1);
		}
		break;
		
		/* We need to set the cursor */
		case WM_SETCURSOR: {
			Uint16 hittest;

			hittest = LOWORD(lParam);
			if ( hittest == HTCLIENT ) {
				return(TRUE);
			}
		}
		break;

		/* We are about to get palette focus! */
		case WM_QUERYNEWPALETTE: {
			WIN_RealizePalette(current_video);
			return(TRUE);
		}
		break;

		/* Another application changed the palette */
		case WM_PALETTECHANGED: {
			WIN_PaletteChanged(current_video, (HWND)wParam);
		}
		break;

		/* We were occluded, refresh our display */
		case WM_PAINT: {
			HDC hdc;
			PAINTSTRUCT ps;

			hdc = BeginPaint(SDL_Window, &ps);
			WIN_WinPAINT(current_video, hdc);
			EndPaint(SDL_Window, &ps);
		}
		break;

		case WM_CLOSE: {
			PostQuitMessage(0);
		}
		return(0);

		case WM_DESTROY: {
			PostQuitMessage(0);
		}
		return(0);

		default: {
			/* Special handling by the video driver */
			return(HandleMessage(current_video,
			                     hwnd, msg, wParam, lParam));
		}
		break;
	}
	return(DefWindowProc(hwnd, msg, wParam, lParam));
}

LONG
 DIB_HandleMessage(_THIS, HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{		
	return(DefWindowProc(hwnd, msg, wParam, lParam));
}

void DIB_DestroyWindow(_THIS)
{
	DestroyWindow(SDL_Window);
}

Uint8 DIB_GetAppState(_THIS)
{
	return SDL_appstate;
}